题目:给定两个字符串str1和str2,再给定3个整数ic、dc和rc,分别代表插入、删除和替换一个字符的代价,返回将str1编辑成str2的最小代价。
例:
str1=”abc”,str2=”adc”,ic=5,dc=3,rc=2.
将’b’替换成’d’代价是最小的,返回代价2。
str1=”abc”,str2=”adc”,ic=5,dc=3,rc=100.
将’b’删掉,然后插入’d’代价是最小的,返回代价5+3=8。
实现:如果str1的长度为M,str2的长度为N。经典动态规划方法可以达到时间复杂度和额外的空间复杂度O(M*N);而通过空间压缩,可以将额外的空间复杂度降为O(min{M,N}+1)。
方法一(经典动态规划):先生成(M+1)*(N+1)的矩阵dp,dp[i][j]的值表示把str1[0,..i-1]编辑成str2[0,..j-1]的最小代价。
注意:这里为什么要加一行和一列空字符串作匹配呢?如果不加,会发现第一行和第一列很难确定它们的值。比如对于第一行str2[j][0],已经有str1的第一个字符str1[0]了,这个时候到底应该替换、删除、还是增加呢,状态不好确定。
- dp[0][0]表示str1空子串编辑成str2空子串的代价,为0.
- 第一列dp[0,..M-1][0],其中dp[i][0]表示将str[0,..i-1]编辑成str2空子串的代价,即将str[0,..i-1]的所有字符删掉,所以dp[i][0]=dc*i。
- 第一行dp[0][0,..N-1],其中dp[0][J]表示将str[0]空子串编辑成str2[0,…j-1]的代价,即将str2[0,.j-1]的所有字符插入到str[0]空子串中,所以dp[0][j]=ic*i。
- 非第一行和第一列,dp[i][j]表示将str1[0,..i-1]编辑成str2[0,..j-1]的最小代价,dp[i][j]的情况可能来自下面的四种情况:
1)str1[0,..i-1] -> str1[0,..i-2]:删除str[i-1],最小代价dc;
str1[0,..i-2] -> str2[0,..j-1]:最小代价dp[i-1][j]。
dp[i-1][j]+dc:这是str1的当前子串str1[0,..i-1]前一个的子串str1[0,..i-2]与str2当前子串str2[j-1]匹配了,因此需要删除str1当前字符str1[i-1]。
2)str1[0,..i-1] -> str1[0,..j-2]:最小代价dp[i][j-1];
str1[0,..j-2] -> str2[0,..j-1]:插入str[i-1],最小代价ic。
dp[i][j-1]+ic:这是str1的当前子串str1[0,..i-1]与str2当前子串str2[j-1]的前一个子串str2[0,..j-2]匹配了,因此需要添加str2当前字符str2[j-1]。
3)str1[i-1]!=str2[j-1]时,
str1[0,..i-2] -> str1[0,..j-2]:最小代价dp[i-1][j-1];
str1[j-1] -> str2[j-1]:替换str[i-1],最小代价rc。
dp[i-1][j-1]+rc:这是str1的当前子串str1[0,..i-1]的前一个子串str1[0,…i-2]与str2当前子串str2[j-1]的前一个子串str2[0,..j-2]匹配了,若str[i-1]!=str2[j-1],则需要将str1[i-1]替换成str2[j-1]。
4)str1[i-1]==str2[j-1]时,
str1[0,..i-2] -> str1[0,..j-2]:最小代价dp[i-1][j-1]。
dp[i-1][j-1]:这是str1的当前子串str1[0,..i-1]的前一个子串str1[0,…i-2]与str2当前子串str2[j-1]的前一个子串str2[0,..j-2]匹配了,若str[i-1]==str2[j-1],则对str1[i-1]和str2[j-1]不需做任何处理。 - 选择最小值的dp[i][j],dp最右下角就是最终结果。
动态规划进阶:数组滚动更新(空间压缩),额外的空间复杂度为O(min{M,N}+1)
本题的数组滚动更新与经典的求最小路径的算法的滚动更新又有不同。在经典求最小路径的算法中,原矩阵中dp[i][j]的值来源于dp[i-1][j]和dp[i][j-1],在dp数组从左往右更新时,dp[j]没有使用相当于原来dp[i-1][j]的值,只使用了dp[j-1],它相当于原来的dp[i][j-1]的值。
而本题中使用了dp[i-1][j]、dp[i][j-1]和dp[i-1][j-1],因此当使用数组滚动更新时,需要用一个变量保存dp[j-1]的值,它相当于原来的dp[i-1][j-1]。
定义dp数组,长度为min{M,N}+1,将较短的字符串作为列对应的字符串。如果长的字符串作为了列对应的字符串,则将插入代价ic和删除代价dc交换。
1 | public class MinCost { |
1 | /** |
1 | //Test |
输出:
1 | str1和str2的动态规划表为: |